﻿using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;

namespace UnitTesting.Fixturing
{
	public class DefaultFixture
	{
		internal ISimpleTypeStrategy SimpleTypeStrategy { get; set; } // setter for inject 

		internal IValueProviderCustomizable ValueProviderCustomizable { get; set; } // setter for inject 

		internal IManyCountSetter ManyCountSetter { get; set; } // setter for inject 

		internal IFreezable Freezable { get; set; } // setter for inject 

		internal IValueProvider ValueProvider { get; set; } // setter for inject 

		protected internal virtual object FixtureOptions
		{
			get { return null; }
		}

		[SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
		public DefaultFixture()
		{
			// ReSharper disable DoNotCallOverridableMethodsInConstructor
			var defaultFixtureParams = CreateDefaultFixtureParams(FixtureOptions);
			// ReSharper restore DoNotCallOverridableMethodsInConstructor
			SimpleTypeStrategy = defaultFixtureParams.SimpleTypeStrategy;
			ValueProviderCustomizable = defaultFixtureParams.ValueProviderCustomizable;
			ManyCountSetter = defaultFixtureParams.ManyCountSetter;
			Freezable = defaultFixtureParams.Freezable;
			ValueProvider = defaultFixtureParams.ValueProvider;
		}

		internal virtual DefaultFixtureParams CreateDefaultFixtureParams(object fixtureOptions)
		{
			return new DefaultFixtureParams(fixtureOptions);
		}

		/* ValueProvider */

		public virtual DefaultFixture AddValueProvider<T>(Func<T> factory)
		{
			return AddValueProvider(new FactoryValueProvider<T>(factory));
		}

		public DefaultFixture AddValueProvider(IValueProvider valueProvider)
		{
			Ensure.IsNotNull(() => valueProvider);
			ValueProviderCustomizable.InsertAtFirst(valueProvider);
			return this;
		}

		public DefaultFixture RemoveValueProvider(IValueProvider valueProvider)
		{
			ValueProviderCustomizable.RemoveLoosely(valueProvider);
			return this;
		}

		public DefaultFixture RemoveRecentValueProvider()
		{
			const int count = 1;
			RemoveRecentValueProvider(count);
			return this;
		}

		public DefaultFixture RemoveRecentValueProvider(int count)
		{
			Enumerable.Range(0, count).ForEach(
				x => ValueProviderCustomizable.RemoveLooselyAt(0));
			return this;
		}

		public virtual DefaultFixture ClearValueProviders()
		{
			ValueProviderCustomizable.Clear();
			return this;
		}

		/* SimpleType */

		public DefaultFixture OnSimpleTypeAsSequential()
		{
			SimpleTypeStrategy.WithSimpleTypeGenerator(new SimpleTypeSequential());
			return this;
		}

		public DefaultFixture OnSimpleTypeAsSequential(int inclusiveMin, int exclusiveMax)
		{
			SimpleTypeStrategy.WithSimpleTypeGenerator(new SimpleTypeSequential(inclusiveMin, exclusiveMax));
			return this;
		}

		public DefaultFixture OnSimpleTypeAsRandom(int seed)
		{
			SimpleTypeStrategy.WithSimpleTypeGenerator(new SimpleTypeDeterministric(seed));
			return this;
		}

		public DefaultFixture OnSimpleTypeAsRandom(int seed, int inclusiveMin, int exclusiveMax)
		{
			SimpleTypeStrategy.WithSimpleTypeGenerator(new SimpleTypeDeterministric(seed, inclusiveMin, exclusiveMax));
			return this;
		}

		public DefaultFixture OnSimpleTypeAsRandom()
		{
			SimpleTypeStrategy.WithSimpleTypeGenerator(new SimpleTypeRandom());
			return this;
		}

		public DefaultFixture OnSimpleTypeAsRandom(int inclusiveMin, int exclusiveMax)
		{
			SimpleTypeStrategy.WithSimpleTypeGenerator(new SimpleTypeRandom(inclusiveMin, exclusiveMax));
			return this;
		}

		public virtual DefaultFixture OnSimpleTypeAsDefault()
		{
			SimpleTypeStrategy.WithSimpleTypeGenerator(new SimpleTypeRandom());
			return this;
		}

		/* Many */

		public DefaultFixture OnManyCountAs(int count)
		{
			ManyCountSetter.Count = count;
			return this;
		}

		public virtual DefaultFixture OnManyCountAsDefault()
		{
			ManyCountSetter.Count = 3;
			return this;
		}

		/* Anonymous & Target */

		internal virtual Anonymous<T> Anonymous<T>()
		{
			return CreateAnonymous<T>(ValueProvider, Freezable);
		}

		internal virtual Anonymous<T> CreateAnonymous<T>(IValueProvider valueProvider, IFreezable freezable)
		{
			return new Anonymous<T>(valueProvider, freezable);
		}

		internal virtual Target<T> Target<T>()
		{
			return CreateTarget<T>(ValueProvider, Freezable, FixtureOptions);
		}

		internal virtual Target<T> CreateTarget<T>(IValueProvider valueProvider, IFreezable freezable, object fixtureOptions)
		{
			return new Target<T>(new TargetParams<T>(valueProvider, freezable, fixtureOptions));
		}

		/* Unfreeze */

		[SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
		public DefaultFixture Unfreeze<T>()
		{
			Freezable.Unfreeze(typeof (T));
			return this;
		}

		public virtual DefaultFixture UnfreezeAll()
		{
			Freezable.UnfreezeAll();
			return this;
		}

		public virtual bool IsFrozen(Type type)
		{
			return Freezable.IsFrozen(type);
		}

		[SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
		public bool IsFrozen<T>()
		{
			return IsFrozen(typeof (T));
		}

		public DefaultFixture FreezeInstance<T>(T instance)
		{
			Freezable.Freeze(instance);
			return this;
		}

		public DefaultFixture FreezeFactory<T>(Func<T> factory)
		{
			Freezable.Freeze(factory);
			return this;
		}

		public void Initialize()
		{
			OnManyCountAsDefault();
			OnSimpleTypeAsDefault();
			UnfreezeAll();
		}

		/* Create Anoymous */

		public T CreateAnonymous<T>()
		{
			return Anonymous<T>().Create();
		}

		public T[] CreateAnonymous<T>(int count)
		{
			return Anonymous<T>().CreateMany(count);
		}

		public T FreezeAnonymous<T>()
		{
			return Anonymous<T>().Freeze();
		}

		/* Create Target */

		[SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed"), SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")]
		public virtual T Create<T>(Action<IWithConstructor> ctorConfig = null,
		                           Action<IWithArgument<T>> argsConfig = null,
		                           Action<IInjector<T>> injectConfig = null)
		{
			var target = CreateTargetWithConfigs(ctorConfig ?? (x => { }), argsConfig ?? (x => { }));

			var instance = target.Create();
			InjectProperties(injectConfig ?? (x => { }), instance);
			return instance;
		}

		Target<T> CreateTargetWithConfigs<T>(Action<IWithConstructor> ctorConfig,
		                                     Action<IWithArgument<T>> argsConfig)
		{
			var target = Target<T>();
			ctorConfig(target);
			argsConfig(target);
			return target;
		}

		void InjectProperties<T>(Action<IInjector<T>> propConfig, T instance)
		{
			var withProperty = CreatePropertyInjector(instance, ValueProvider);
			propConfig(withProperty);
		}

		internal virtual IInjector<T> CreatePropertyInjector<T>(T instance, IValueProvider valueProvider)
		{
			return new Injector<T>(instance, valueProvider);
		}

		[SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed"), SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")]
		public virtual T Freeze<T>(Action<IWithConstructor> ctorConfig = null,
		                           Action<IWithArgument<T>> argsConfig = null,
		                           Action<IInjector<T>> injectConfig = null)
		{
			var target = CreateTargetWithConfigs(ctorConfig ?? (x => { }), argsConfig ?? (x => { }));
			var instance = target.Freeze();
			InjectProperties(injectConfig ?? (x => { }), instance);
			return instance;
		}
	}
}